/*
 * Options.java
 *
 * Created on July 20, 2007, 8:46 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */
package filecopier.options;

import java.awt.Rectangle;
import java.io.File;
import java.io.FilenameFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Calendar;
import java.util.Properties;
import java.util.regex.Pattern;
import java.util.Formatter;
import java.util.SimpleTimeZone;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Frame;
import javax.swing.UIManager;
import jargs.gnu.CmdLineParser;

import org.apache.log4j.Level;

import filecopier.Singleton;
import filecopier.FileCopierException;
import filecopier.script.ScriptPath;
import filecopier.gui.restore.RestoreScriptPathWrapper;

import custom.util.MRUException;
import custom.util.MostRecentlyUsed;
import custom.io.RegExFilenameFilter;

/**
 *
 * @author david
 */
public class Options {

    /** The different choices for when a file exists. */
    public static enum RenameType {

        Overwrite, Skip, RenameAuto, RenameManual
    }

    public static enum SrchFilter {

        FileCopyFilter, DirCopyFilter, DirFlattenFilter, DirMergeFilter
    }

    public static enum ActionMode {

        Unknown, Copy, Restore
    }

    public static enum UIMode {

        Console, Gui
    }

    public static enum CopyScriptType {

        From, To
    }

    public static enum TabIndex {

        Copy, Restore, ExistingFiles, SrcDirs, Miscellaneous
    }
    // Copy Index tab
    public static final int COPY_IDX = 0;
    // Restore Index tab
    public static final int RESTORE_IDX = 1;
    // Existing Files Index tab
    public static final int EXISTINGFILES_IDX = 2;
    // Src Dirs Index tab
    public static final int SRCDIRS_IDX = 3;
    // Miscellaneous tab
    public static final int MISC_IDX = 4;
    public static final int FILE_COPY_FILTER = 0;
    public static final int DIR_COPY_FILTER = 1;
    public static final int DIR_FLATTEN_FILTER = 2;
    public static final int DIR_MERGE_FILTER = 3;
    /** Final int value representing the From Index of the array of MostRecentlyUsed objects. */
    public static final int FROM_IDX = 0;
    /** Final int value representing the To Index of the array of MostRecentlyUsed objects. */
    public static final int TO_IDX = 1;
    public static final SrchFilter[] srchFilterValues = SrchFilter.values();
    /** Default values */
    public static final Boolean defaultBuildRestoreScript = new Boolean(true);
    public static final Boolean defaultCaseSensitiveFilter = new Boolean(false);
    //public static final Boolean defaultCopyRootDirectory = new Boolean(true);
    public static final Boolean defaultExclusiveFilter = new Boolean(false);
    //public static final Boolean defaultMergeDirectories = new Boolean(false);
    public static final Boolean defaultPromptForOverwrite = new Boolean(false);
    public static final Boolean defaultPromptForRestore = new Boolean(false);
    public static final Boolean defaultRegExFilter = new Boolean(false);
    public static final Boolean defaultUpdateOnly = new Boolean(false);
    public static final Boolean defaultUseGMT = new Boolean(false);
    public static final Boolean defaultUseWindowDecorations = new Boolean(false);
    public static final Integer defaultMaxDirCopyLevel = new Integer(0);
    public static final Integer defaultMaxDirMergeLevel = new Integer(custom.util.FileCopy.NO_MERGE);
    public static final Integer defaultMaxDirFlattenLevel = new Integer(custom.util.FileCopy.NO_FLATTEN);
    public static final String defaultRenameFmtStr = "_%tY%tm%td_%tH%tM%tS%tL";
    public static final Level defaultLogLevel = Level.INFO;
    public static final RenameType defaultRenameType = RenameType.RenameAuto;
    public static final FilenameFilter defaultRegExFilenameFilter = new RegExFilenameFilter("");
    public static final String defaultRegExFilenameFilterStr = "";
    public static final UIMode defaultUIMode = UIMode.Gui;
    public final static TabIndex defaultTabIndex = TabIndex.Copy;
    public final static int defaultMostRecentlyUsed = 4;
    public static UIManager.LookAndFeelInfo defaultLookAndFeel = null;
    private File fileCopierIni = null;
    private Properties fileCopierProperties = null;
    /** Enum type that stores the way in which the user desires to rename a file that already exists. */
    private RenameType renameType = defaultRenameType;
    private Level logLevel = defaultLogLevel;
    private boolean logLevelChanged = false;
    private TabIndex tabIndex = null;
    private UIMode uiMode = defaultUIMode;
    /** String that holds the location of temporary script files */
    private File tempScriptDirectory = null;
    /**
     * Boolean value that indicates if a restore script should be built if necessary when a copy
     * command is run.
     */
    private Boolean buildRestoreScript = defaultBuildRestoreScript;
    /** Boolean value that indicates if each overwrite in a copy operation should be verified before doing so. */
    private Boolean promptForOverwrite = defaultPromptForOverwrite;
    /** Boolean value that indicates if files are to be copied only if the destination file is older than the source file. */
    private Boolean updateOnly = defaultUpdateOnly;
    /** Boolean value that indicates if each restore in a restore operation should be verified before doing so. */
    private Boolean promptForRestore = defaultPromptForRestore;
    // These two variables are set to true if the user requested a print out of the copy/restore path.
    private boolean getCopyScriptPath = false;
    private boolean getRestoreScriptPath = false;
    // This variable is set to true if the user requested a print out of the version.
    private boolean getVersion = false;
    // These two variables are set to true if the user set the copy/restore path.
    //private boolean setPath = false;
    private boolean setCopyScriptPath = false;
    private boolean setRestoreScriptPath = false;
    /** Boolean value that indicates if source file is a directory and it exists at its destination, the two directories should
     * be merged. */
    //private Boolean mergeDirectories = defaultMergeDirectories;
    /** Boolean value that indicates whether a file search string is case sensitive */
    private Boolean[] caseSensitiveFilter = new Boolean[]{defaultCaseSensitiveFilter, defaultCaseSensitiveFilter};
    /** Boolean value that indicates whether a file search string is a regular expression */
    private Boolean[] regExFilter = new Boolean[]{defaultRegExFilter, defaultRegExFilter};
    /** Boolean value that indicates whether a file search filter is inclusive or exclusive */
    private Boolean[] exclusiveFilter = new Boolean[]{defaultExclusiveFilter, defaultExclusiveFilter};
    /**  Filename filter for regular expression */
    private FilenameFilter[] regExFilenameFilter = null;
    /** String that stores what the user enters as the format for the suffix to be used when renaming an existing file with its
     * last modified date. */
    private String renameFmtStr = defaultRenameFmtStr;
    private RenameFmtString renameFmtString = null;
    /** Boolean that indicates whether the rename format string should be treated as Greenwich meantime or not. */
    private Boolean useGMT = defaultUseGMT;
    public static final int MAX_DIR_COPY_IDX = 0;
    public static final int MAX_DIR_FLATTEN_IDX = 1;
    public static final int MAX_DIR_MERGE_IDX = 2;
    /** Integer that indicates the max level of directories to copy if the source is a directory. A value of 0 means copy
     *  all levels. */
    /** Integer that indicates the max level of directories to maxMerge if the source is a directory. A value of 0 means
     *  maxMerge all levels. A value of -1(the default) means maxMerge no levels */
    /** Integer that indicates the max level of directories to flatten if the source is a directory. A value of 0 means
     * flatten all levels. A value of -1 (the default) means flatten no levels. */
    private Integer[] maxDirLevels = new Integer[]{defaultMaxDirCopyLevel, defaultMaxDirFlattenLevel, defaultMaxDirMergeLevel};
    private String[] caseSensitiveFilterKeys = new String[]{defaultCaseSensitiveFilter.toString(), defaultCaseSensitiveFilter.toString(), defaultCaseSensitiveFilter.toString(), defaultCaseSensitiveFilter.toString()};
    private String[] regExFilterKeys = new String[]{defaultRegExFilter.toString(), defaultRegExFilter.toString(), defaultRegExFilter.toString(), defaultRegExFilter.toString()};
    private String[] exclusiveFilterKeys = new String[]{defaultExclusiveFilter.toString(), defaultExclusiveFilter.toString(), defaultExclusiveFilter.toString(), defaultExclusiveFilter.toString()};
    private String[] regExFilenameFilterKeys = new String[]{defaultRegExFilenameFilterStr, defaultRegExFilenameFilterStr, defaultRegExFilenameFilterStr, defaultRegExFilenameFilterStr};
    // These three variables will hold the from script, to script and restore script names if they were passed as arguments
    private String fromScriptStr = null;
    private String toScriptStr = null;
    private String restoreScriptStr = null;
    // These two variables hold the restore path and the copy path. If a user selects a copy script or a restore script without
    // a full path, FileCopier will look for the file in the respective path.
    private ScriptPath copyPath = null;
    private RestoreScriptPathWrapper restorePathWrapper = null;
    /** Active look and feel for the app */
    private UIManager.LookAndFeelInfo activeLookAndFeel = null;
    private Rectangle bounds = null;
    /**
     * Object that gets and stores the list of most recently used from scripts/to scripts from
     * FileCopier.properties.
     */
    private MostRecentlyUsed[] mru;
    // Use/don't use window decorations.
    private Boolean useWindowDecorations = defaultUseWindowDecorations;

    /** Creates a new instance of Options */
    public Options(String[] args) throws FileCopierException, CmdLineParser.OptionException {
        initFileCopierProperties();
        parseCmdLine(args);
        initProperties();
    }

    public boolean buildRestoreScript() {
        synchronized (buildRestoreScript) {
            return buildRestoreScript.booleanValue();
        }
    }

    public boolean getCaseSensitiveFilter(SrchFilter sf) {
        int idx = sf.ordinal();
        synchronized (caseSensitiveFilter[idx]) {
            return caseSensitiveFilter[idx].booleanValue();
        }
    }

    public ScriptPath getCopyPath() {
        synchronized (copyPath) {
            return copyPath;
        }
    }

    public int getMaxDirFlattenLevel() {
        synchronized (maxDirLevels[MAX_DIR_FLATTEN_IDX]) {
            return maxDirLevels[MAX_DIR_FLATTEN_IDX];
        }
    }

    public boolean didLogLevelChange() {
        return logLevelChanged;
    }

    public Rectangle getBounds() {
        return bounds;
    }

    public boolean getExclusiveFilter(SrchFilter sf) {
        int idx = sf.ordinal();
        synchronized (exclusiveFilter[idx]) {
            return exclusiveFilter[idx].booleanValue();
        }
    }

    public String getFromScriptStr() {
        return fromScriptStr;
    }

    public String getToScriptStr() {
        return toScriptStr;
    }

    public String getRestoreScriptStr() {
        return restoreScriptStr;
    }

    public Level getLogLevel() {
        if (logLevel != null) {
            synchronized (logLevel) {
                return logLevel;
            }
        }
        return logLevel;
    }

    public UIManager.LookAndFeelInfo getLookAndFeel() {
        synchronized (activeLookAndFeel) {
            return activeLookAndFeel;
        }
    }

    public int getMaxDirLevel(int idx) {
        if (idx < MAX_DIR_COPY_IDX || idx > MAX_DIR_MERGE_IDX) {
            throw new IllegalArgumentException("getMaxDirLevel() was passed an index that is out of range.");
        }
        synchronized (maxDirLevels[idx]) {
            return maxDirLevels[idx].intValue();
        }
    }

    public int getMaxDirCopyLevel() {
        synchronized (maxDirLevels[MAX_DIR_COPY_IDX]) {
            return maxDirLevels[MAX_DIR_COPY_IDX].intValue();
        }
    }

    public void logLevelUpdated() {
        logLevelChanged = false;
    }

    public int getMaxDirMergeLevel() {
        synchronized (maxDirLevels[MAX_DIR_MERGE_IDX]) {
            return maxDirLevels[MAX_DIR_MERGE_IDX].intValue();
        }
    }

    public boolean promptForOverwrite() {
        synchronized (promptForOverwrite) {
            return promptForOverwrite.booleanValue();
        }
    }

    public boolean promptForRestore() {
        synchronized (promptForRestore) {
            return promptForRestore.booleanValue();
        }
    }

    public FilenameFilter getRegExFilenameFilter(SrchFilter sf) {
        int idx = sf.ordinal();
        synchronized (regExFilenameFilter[idx]) {
            return regExFilenameFilter[idx];
        }
    }

    public boolean getRegExFilter(SrchFilter sf) {
        int idx = sf.ordinal();
        synchronized (regExFilter[idx]) {
            return regExFilter[idx].booleanValue();
        }
    }

    public RenameFmtString getRenameFmtString() {
        return renameFmtString;
    }

    public RenameType getRenameType() {
        synchronized (renameType) {
            return renameType;
        }
    }

    public RestoreScriptPathWrapper getRestorePathWrapper() {
        synchronized (restorePathWrapper) {
            return restorePathWrapper;
        }
    }

    public TabIndex getTabIndex() {
        return tabIndex;
    }

    public File getTempScriptDirectory() {
        return tempScriptDirectory;
    }

    public boolean getUpdateOnly() {
        synchronized (updateOnly) {
            return updateOnly.booleanValue();
        }
    }

    public UIMode getUIMode() {
        return uiMode;
    }

    public boolean getUseGMT() {
        synchronized (useGMT) {
            return useGMT.booleanValue();
        }
    }

    public MostRecentlyUsed[] getMostRecentlyUsed() {
        synchronized (mru) {
            return mru;
        }
    }
    
    public boolean useWindowDecorations() {
        synchronized(useWindowDecorations) {
            return useWindowDecorations.booleanValue();
        }
    }
    
    public void setWindowDecorations(boolean set) {
        useWindowDecorations = set;
    }

    private Rectangle initBoundsFromProperties() {
        int x = new Integer(fileCopierProperties.getProperty("Window.x", "0"));
        int y = new Integer(fileCopierProperties.getProperty("Window.y", "0"));
        int height = new Integer(fileCopierProperties.getProperty("Window.height", "394"));
        int width = new Integer(fileCopierProperties.getProperty("Window.width", "439"));
        bounds = new Rectangle(x, y, width, height);
        return bounds;
    }

    /**
     * This method returns the value of BuildRestoreScript in fileCopierProperties.
     *
     * @return java.lang.boolean
     */
    private Boolean initBuildRestoreScriptFromProperties() {
        String status = fileCopierProperties.getProperty("BuildRestoreScript", defaultBuildRestoreScript.toString());
        buildRestoreScript = new Boolean(status);
        return (buildRestoreScript);
    }

    private Boolean initCaseSensitiveFilterFromProperties(String nameFilter) {
        String status = fileCopierProperties.getProperty(nameFilter, defaultCaseSensitiveFilter.toString());
        Boolean caseSensitiveFilter = new Boolean(status);
        return caseSensitiveFilter;
    }

    private ScriptPath initCopyPathFromProperties() {
        ScriptPath copyPath = new ScriptPath("filecopier.copy.path", ";", System.getProperty("filecopier.scripts.copy"),
                fileCopierProperties.getProperty("CopyPath", System.getProperty("filecopier.scripts.copy")));

        // Set the property "filecopier.scripts.copy" to be the same as what we got out of the filecopier.properties file.
        System.setProperty("filecopier.scripts.copy", fileCopierProperties.getProperty("CopyPath", System.getProperty("filecopier.scripts.copy")));
        return copyPath;
    }

    private Integer initMaxDirFlattenLevelFromProperties() {
        String status = fileCopierProperties.getProperty("MaxDirFlattenLevel", defaultMaxDirFlattenLevel.toString());
        maxDirLevels[MAX_DIR_FLATTEN_IDX] = new Integer(status);
        return maxDirLevels[MAX_DIR_FLATTEN_IDX];
    }
    
    private Boolean initExclusiveFilterFromProperties(String filenameFilter) {
        String status = fileCopierProperties.getProperty(filenameFilter, defaultExclusiveFilter.toString());
        Boolean exclusiveFilter = new Boolean(status);
        return exclusiveFilter;
    }

    private Properties initFileCopierProperties() throws FileCopierException {
        try {
            // Create the file object.
            fileCopierIni = new File(System.getProperty("filecopier.home") +
                    System.getProperty("file.separator") + "FileCopier.properties");
            // Create the file itself.
            fileCopierIni.createNewFile();

            fileCopierProperties = new Properties();

            FileInputStream fis = null;
            try {
                fis = new FileInputStream(fileCopierIni);
                fileCopierProperties.load(fis);
            } finally {
                fis.close();
            }

            return fileCopierProperties;
        } catch (IOException e) {
            throw new FileCopierException(e, e);
        }
    }

    private Level initLogLevelFromProperties() {
        Level logLevel = null;

        String status = fileCopierProperties.getProperty("LogLevel", "");
        if (status.equalsIgnoreCase("debug")) {
            logLevel = Level.DEBUG;
        } else if (status.equalsIgnoreCase("warn")) {
            logLevel = Level.WARN;
        } else if (status.equalsIgnoreCase("error")) {
            logLevel = Level.ERROR;
        } else {
            logLevel = defaultLogLevel;
        }

        return logLevel;
    }

    private UIManager.LookAndFeelInfo initLookAndFeelFromProperties() {
        // First, get all the installed look and feels in our environment.
        UIManager.LookAndFeelInfo[] installedLookAndFeels = UIManager.getInstalledLookAndFeels();
        
        // Now, get the look and feel name from the properties file.
        String name = fileCopierProperties.getProperty("LookAndFeel", "Metal");

        // Make sure what we got from the properties file is a look and feel that is available to us.
        for (UIManager.LookAndFeelInfo lfi : installedLookAndFeels) {
            if (lfi.getName().equalsIgnoreCase(name)) {
                // We found the name. Now, set the activeLookAndFeel to the actual class name of the look and feel.
                activeLookAndFeel = lfi;
            }
            if (lfi.getName().equalsIgnoreCase("Metal")) {
                // Get the metal(java) look and feel. This will be the default if we cannot find the look and feel that was
                // saved in the properties file.
                defaultLookAndFeel = lfi;
            }
        }
        if (activeLookAndFeel == null) {
            if (defaultLookAndFeel != null) {
                // The name of the look and feel we got from the properties file is not available. Get the java look and
                // feel class name.
                activeLookAndFeel = defaultLookAndFeel;
            } else {
                // Just set the activeLookAndFeel to the first in the list of installed look and feels.
                activeLookAndFeel = installedLookAndFeels[0];
                defaultLookAndFeel = installedLookAndFeels[0];
            }
        }
        return activeLookAndFeel;
    }

    private Integer initMaxDirCopyLevelFromProperties() {
        String status = fileCopierProperties.getProperty("MaxDirCopyLevel", defaultMaxDirCopyLevel.toString());
        maxDirLevels[MAX_DIR_COPY_IDX] = new Integer(status);
        return (maxDirLevels[MAX_DIR_COPY_IDX]);
    }

    private Integer initMaxDirMergeLevelFromProperties() {
        String status = fileCopierProperties.getProperty("MaxDirMergeLevel", defaultMaxDirMergeLevel.toString());
        maxDirLevels[MAX_DIR_MERGE_IDX] = new Integer(status);
        return (maxDirLevels[MAX_DIR_MERGE_IDX]);
    }

    /**
     * Intializes and loads an MostRecentlyUsed object.
     *
     * @param section entry in FileCopier.properties from which to load data.
     * @return custom.utility.MostRecentlyUsed
     * @throws FileNotFoundException
     * @throws IOException
     * @throws MRUException
     */
    private MostRecentlyUsed initMRU(final String section) throws FileCopierException {
        try {
            MostRecentlyUsed mru = new MostRecentlyUsed(fileCopierIni, section, defaultMostRecentlyUsed);
            mru.load();
            return mru;
        } catch (MRUException e) {
            throw new FileCopierException(e, e);
        }
    }

    private Boolean initPromptForOverwriteFromProperties() {
        String status = fileCopierProperties.getProperty("PromptForOverwrite", defaultPromptForOverwrite.toString());
        promptForOverwrite = new Boolean(status);
        return (promptForOverwrite);
    }

    private Boolean initPromptForRestoreFromProperties() {
        String status = fileCopierProperties.getProperty("PromptForRestore", defaultPromptForRestore.toString());
        promptForRestore = new Boolean(status);
        return (promptForRestore);
    }

    private void initProperties() throws FileCopierException {
        mru = new MostRecentlyUsed[TO_IDX + 1];
        mru[FROM_IDX] = initMRU("FromScripts");
        mru[TO_IDX] = initMRU("ToScripts");

        regExFilenameFilter = new FilenameFilter[DIR_MERGE_FILTER + 1];
        regExFilenameFilterKeys = new String[DIR_MERGE_FILTER + 1];
        regExFilenameFilterKeys[FILE_COPY_FILTER] = "FileNameCopyFilter";
        regExFilenameFilterKeys[DIR_COPY_FILTER] = "DirNameCopyFilter";
        regExFilenameFilterKeys[DIR_FLATTEN_FILTER] = "DirNameFlattenFilter";
        regExFilenameFilterKeys[DIR_MERGE_FILTER] = "DirNameMergeFilter";


        caseSensitiveFilter = new Boolean[DIR_MERGE_FILTER + 1];
        caseSensitiveFilterKeys = new String[DIR_MERGE_FILTER + 1];
        caseSensitiveFilterKeys[FILE_COPY_FILTER] = "CaseSensitiveFileCopyFilter";
        caseSensitiveFilterKeys[DIR_COPY_FILTER] = "CaseSensitiveDirCopyFilter";
        caseSensitiveFilterKeys[DIR_FLATTEN_FILTER] = "CaseSensitiveDirFlattenFilter";
        caseSensitiveFilterKeys[DIR_MERGE_FILTER] = "CaseSensitiveDirMergeFilter";

        regExFilter = new Boolean[DIR_MERGE_FILTER + 1];
        regExFilterKeys = new String[DIR_MERGE_FILTER + 1];
        regExFilterKeys[FILE_COPY_FILTER] = "RegExFileCopyFilter";
        regExFilterKeys[DIR_COPY_FILTER] = "RegExDirCopyFilter";
        regExFilterKeys[DIR_FLATTEN_FILTER] = "RegExDirFlattenFilter";
        regExFilterKeys[DIR_MERGE_FILTER] = "RegExDirMergeFilter";

        exclusiveFilter = new Boolean[DIR_MERGE_FILTER + 1];
        exclusiveFilterKeys = new String[DIR_MERGE_FILTER + 1];
        exclusiveFilterKeys[FILE_COPY_FILTER] = "ExclusiveFileCopyFilter";
        exclusiveFilterKeys[DIR_COPY_FILTER] = "ExclusiveDirCopyFilter";
        exclusiveFilterKeys[DIR_FLATTEN_FILTER] = "ExclusiveDirFlattenFilter";
        exclusiveFilterKeys[DIR_MERGE_FILTER] = "ExclusiveDirMergeFilter";

        try {
            FileInputStream fis = null;
            try {
                fis = new FileInputStream(fileCopierIni);
                fileCopierProperties.load(fis);
                buildRestoreScript = initBuildRestoreScriptFromProperties();
                promptForOverwrite = initPromptForOverwriteFromProperties();
                promptForRestore = initPromptForRestoreFromProperties();
                maxDirLevels[MAX_DIR_FLATTEN_IDX] = initMaxDirFlattenLevelFromProperties();
                maxDirLevels[MAX_DIR_MERGE_IDX] = initMaxDirMergeLevelFromProperties();
                maxDirLevels[MAX_DIR_COPY_IDX] = initMaxDirCopyLevelFromProperties();
                renameType = initRenameTypeFromProperties();
                logLevel = initLogLevelFromProperties();
                tabIndex = initTabIndexFromProperties();
                activeLookAndFeel = initLookAndFeelFromProperties();
                updateOnly = initUpdateOnlyFromProperties();
                useGMT = initUseGMTFromProperties();
                renameFmtString = initRenameFmtStringFromProperties();
                copyPath = initCopyPathFromProperties();
                restorePathWrapper = initRestorePathWrapperFromProperties();
                useWindowDecorations = initUseWindowDecorationsFromProperties();
                if (uiMode == UIMode.Gui) {
                    bounds = initBoundsFromProperties();
                }

                // These booleans are used to set the type of file name filter (file or directory). Only the file copy
                // filter (the first one in the regexFilenameFilter array) is a file filter. The rest are dir filters.
                boolean[] filterSubNames = {false, true, true, true};
                boolean[] filterFileNames = {true, false, false, false};
                // This boolean sets the accept value for if we initialize the filename filter to a NullFilenameFilter
                // object. The two copy filters default to true. The flatten and merge filters default to false.
                boolean[] toAccept = {true, true, false, false};
                for (SrchFilter sf : srchFilterValues) {
                    int idx = sf.ordinal();

                    regExFilenameFilter[idx] = initRegExFilenameFilterFromProperties(sf, filterSubNames[idx], filterFileNames[idx], toAccept[idx]);
                }
            } finally {
                fis.close();
            }
        } catch (IOException e) {
            throw new FileCopierException(e, e);
        }
    }

    private FilenameFilter initRegExFilenameFilterFromProperties(SrchFilter sf, boolean filterSubNames, boolean filterFileNames, boolean toAccept) {
        int idx = sf.ordinal();

        String regexString = fileCopierProperties.getProperty(regExFilenameFilterKeys[idx], defaultRegExFilenameFilterStr);
        // Initialize the filters first.
        caseSensitiveFilter[idx] = initCaseSensitiveFilterFromProperties(caseSensitiveFilterKeys[idx]);
        regExFilter[idx] = initRegExFilterFromProperties(regExFilterKeys[idx]);
        exclusiveFilter[idx] = initExclusiveFilterFromProperties(exclusiveFilterKeys[idx]);

        FilenameFilter filenameFilter = setRegExFilenameFilter(sf, regexString, caseSensitiveFilter[idx],
                exclusiveFilter[idx], regExFilter[idx], filterSubNames, filterFileNames, toAccept);

        return filenameFilter;
    }

    private Boolean initRegExFilterFromProperties(String nameFilter) {
        String status = fileCopierProperties.getProperty(nameFilter, defaultRegExFilter.toString());
        Boolean regExFilter = new Boolean(status);
        return regExFilter;
    }

    private RenameFmtString initRenameFmtStringFromProperties() {
        if (renameFmtString == null) {
            renameFmtStr = fileCopierProperties.getProperty("RenameFmtString", defaultRenameFmtStr);
            renameFmtString = new RenameFmtString(renameFmtStr, useGMT ? Calendar.getInstance(new SimpleTimeZone(0, "GMT")) : Calendar.getInstance());
        }
        return renameFmtString;
    }

    /**
     * This method gets the rename type from the FileCopier.properties file and puts it in a
     * RenameType object.
     *
     * @return filecopier.copy.CopyCommand.RenameType
     */
    private RenameType initRenameTypeFromProperties() {
        RenameType renameType;

        String status = fileCopierProperties.getProperty("IfFileExists", "");
        if (status.equalsIgnoreCase("RenameManual")) {
            renameType = RenameType.RenameManual;
        } else if (status.equalsIgnoreCase("Overwrite")) {
            renameType = RenameType.Overwrite;
        } else if (status.equalsIgnoreCase("Skip")) {
            renameType = RenameType.Skip;
        } else {
            renameType = RenameType.RenameAuto;
        }

        return renameType;
    }

    private RestoreScriptPathWrapper initRestorePathWrapperFromProperties() {
        ScriptPath restorePath = new ScriptPath("filecopier.script.path", ";", System.getProperty("filecopier.scripts.restore"),
                fileCopierProperties.getProperty("RestorePath", System.getProperty("filecopier.scripts.restore")));

        // Set the property "filecopier.scripts.restore" to be the same as what we got out of the filecopier.properties file.
        System.setProperty("filecopier.scripts.restore", fileCopierProperties.getProperty("RestorePath", System.getProperty("filecopier.scripts.restore")));

        return new RestoreScriptPathWrapper(restorePath);
    }

    private TabIndex initTabIndexFromProperties() {
        String status = fileCopierProperties.getProperty("Tab", "Copy");
        if (status.equals(TabIndex.Copy.toString())) {
            tabIndex = TabIndex.Copy;
        } else if (status.equals(TabIndex.ExistingFiles.toString())) {
            tabIndex = TabIndex.ExistingFiles;
        } else if (status.equals(TabIndex.Miscellaneous.toString())) {
            tabIndex = TabIndex.Miscellaneous;
        } else if (status.equals(TabIndex.Restore.toString())) {
            tabIndex = TabIndex.Restore;
        } else {
            tabIndex = TabIndex.SrcDirs;
        }
        return tabIndex;
    }

    private Boolean initUpdateOnlyFromProperties() {
        String status = fileCopierProperties.getProperty("UpdateOnly", defaultUpdateOnly.toString());
        updateOnly = new Boolean(status);
        return (updateOnly);
    }

    private UIMode initUIModeFromProperties() {
        String status = fileCopierProperties.getProperty("UIMode", defaultUIMode.toString());
        return (UIMode.valueOf(status));
    }

    private Boolean initUseGMTFromProperties() {
        String status = fileCopierProperties.getProperty("GMT", defaultUseGMT.toString());
        useGMT = new Boolean(status);
        return useGMT;
    }
    
    private Boolean initUseWindowDecorationsFromProperties() {
        String status = fileCopierProperties.getProperty("WindowDecorations", defaultUseWindowDecorations.toString());
        useWindowDecorations = new Boolean(status);
        return useWindowDecorations;
    }

    private void parseCmdLine(String[] args) throws FileCopierException, CmdLineParser.OptionException {
        if (args != null && args.length > 0) {
            FileOutputStream fos = null;

            try {
                // Store the info from the command line into the filecopier.proterties file
                fos = new FileOutputStream(fileCopierIni);

                setOptionsToDefaults();
                CmdLineParser parser = new CmdLineParser();
                CmdLineParser.Option renameFmtStr = parser.addStringOption('a', "rename-fmt-string");
                CmdLineParser.Option uiMode = parser.addBooleanOption('c', "console");
                CmdLineParser.Option dirNameCopyFilter = parser.addStringOption('d', "dir-name-copy-filter");
                CmdLineParser.Option dirNameFlattenFilter = parser.addStringOption('D', "dir-name-flatten-filter");
                CmdLineParser.Option fileExistAction = parser.addStringOption('e', "file-exists");
                CmdLineParser.Option fromScript = parser.addStringOption('f', "from-script");
                CmdLineParser.Option maxFlatten = parser.addIntegerOption('F', "max-dir-flatten-level");
                CmdLineParser.Option regexFileFilter = parser.addBooleanOption('g', "regex-file-copy-filter");
                CmdLineParser.Option gmt = parser.addBooleanOption('G', "gmt");
                CmdLineParser.Option regexDirFlattenFilter = parser.addBooleanOption('h', "regex-dir-flatten-filter");
                CmdLineParser.Option caseSensitiveDirFlattenFilter = parser.addBooleanOption('H', "case-sensitive-dir-flatten-filter");
                CmdLineParser.Option regexDirMergeFilter = parser.addBooleanOption('i', "regex-dir-merge-filter");
                CmdLineParser.Option caseSensitiveDirMergeFilter = parser.addBooleanOption('I', "case-sensitive-dir-merge-filter");
                CmdLineParser.Option regexDirCopyFilter = parser.addBooleanOption('j', "regex-dir-copy-filter");
                CmdLineParser.Option exclusiveDirFlattenFilter = parser.addBooleanOption('k', "exclusive-dir-flatten-filter");
                CmdLineParser.Option exclusiveDirMergeFilter = parser.addBooleanOption('K', "exclusive-dir-merge-filter");
                CmdLineParser.Option logLevel = parser.addStringOption('l', "log");
                CmdLineParser.Option maxMerge = parser.addIntegerOption('m', "max-dir-merge-level");
                CmdLineParser.Option fileNameCopyFilter = parser.addStringOption('n', "file-name-copy-filter");
                CmdLineParser.Option dirNameMergeFilter = parser.addStringOption('N', "dir-name-merge-filter");
                CmdLineParser.Option setCopyScriptPathOption = parser.addStringOption('o', "set-copy-path");
                CmdLineParser.Option getCopyScriptPathOption = parser.addBooleanOption('O', "get-copy-path");
                CmdLineParser.Option promptRestore = parser.addBooleanOption('p', "prompt-restore");
                CmdLineParser.Option setRestoreScriptPathOption = parser.addStringOption('q', "set-restore-path");
                CmdLineParser.Option getRestoreScriptPathOption = parser.addBooleanOption('Q', "get-restore-path");
                CmdLineParser.Option restoreScript = parser.addStringOption('r', "restore-script");
                CmdLineParser.Option noBuildRestoreScript = parser.addBooleanOption('R', "no-restore");
                CmdLineParser.Option caseSensitiveFileCopyFilter = parser.addBooleanOption('s', "case-sensitive-file-copy-filter");
                CmdLineParser.Option caseSensitiveDirCopyFilter = parser.addBooleanOption('S', "case-sensitive-dir-copy-filter");
                CmdLineParser.Option toScript = parser.addStringOption('t', "to-script");
                CmdLineParser.Option update = parser.addBooleanOption('u', "update");
                CmdLineParser.Option getVersionOption = parser.addBooleanOption('v', "version");
                CmdLineParser.Option promptOverwrite = parser.addBooleanOption('w', "prompt-overwrite");
                CmdLineParser.Option exclusiveFileCopyFilter = parser.addBooleanOption('x', "exclusive-file-copy-filter");
                CmdLineParser.Option exclusiveDirCopyFilter = parser.addBooleanOption('X', "exclusive-dir-copy-filter");
                CmdLineParser.Option maxCopy = parser.addIntegerOption('y', "max-dir-copy-level");

                parser.parse(args);

                fromScriptStr = (String) parser.getOptionValue(fromScript);
                toScriptStr = (String) parser.getOptionValue(toScript);
                restoreScriptStr = (String) parser.getOptionValue(restoreScript);

                this.uiMode = (Boolean) parser.getOptionValue(uiMode, false) ? UIMode.Console : UIMode.Gui;

                String renameFmtString = (String) parser.getOptionValue(renameFmtStr, defaultRenameFmtStr);
                fileCopierProperties.setProperty("RenameFmtString", renameFmtString);

                RenameType renameType = setRenameType(fileExistAction,
                        (String) parser.getOptionValue(fileExistAction, defaultRenameType.toString()));
                fileCopierProperties.setProperty("IfFileExists", renameType.toString());

                boolean caseSensitiveFilter = (Boolean) parser.getOptionValue(caseSensitiveFileCopyFilter,
                        defaultCaseSensitiveFilter);
                fileCopierProperties.setProperty("CaseSensitiveFileCopyFilter", Boolean.toString(caseSensitiveFilter));

                boolean regexFilter = (Boolean) parser.getOptionValue(regexFileFilter, defaultRegExFilter);
                fileCopierProperties.setProperty("RegExFileCopyFilter", Boolean.toString(regexFilter));

                boolean exclusiveFilter = (Boolean) parser.getOptionValue(exclusiveFileCopyFilter, defaultExclusiveFilter);
                fileCopierProperties.setProperty("ExclusiveFileCopyFilter", Boolean.toString(exclusiveFilter));

                caseSensitiveFilter = (Boolean) parser.getOptionValue(caseSensitiveDirCopyFilter, defaultCaseSensitiveFilter);
                fileCopierProperties.setProperty("CaseSensitiveDirCopyFilter", Boolean.toString(caseSensitiveFilter));

                regexFilter = (Boolean) parser.getOptionValue(regexDirCopyFilter, defaultRegExFilter);
                fileCopierProperties.setProperty("RegExDirCopyFilter", Boolean.toString(regexFilter));

                exclusiveFilter = (Boolean) parser.getOptionValue(exclusiveDirCopyFilter, defaultExclusiveFilter);
                fileCopierProperties.setProperty("ExclusiveDirCopyFilter", Boolean.toString(exclusiveFilter));

                caseSensitiveFilter = (Boolean) parser.getOptionValue(caseSensitiveDirFlattenFilter, defaultCaseSensitiveFilter);
                fileCopierProperties.setProperty("CaseSensitiveDirFlattenFilter", Boolean.toString(caseSensitiveFilter));

                regexFilter = (Boolean) parser.getOptionValue(regexDirFlattenFilter, defaultRegExFilter);
                fileCopierProperties.setProperty("RegExDirFlattenFilter", Boolean.toString(regexFilter));

                exclusiveFilter = (Boolean) parser.getOptionValue(exclusiveDirFlattenFilter, defaultExclusiveFilter);
                fileCopierProperties.setProperty("ExclusiveDirFlattenFilter", Boolean.toString(exclusiveFilter));

                caseSensitiveFilter = (Boolean) parser.getOptionValue(caseSensitiveDirMergeFilter, defaultCaseSensitiveFilter);
                fileCopierProperties.setProperty("CaseSensitiveDirMergeFilter", Boolean.toString(caseSensitiveFilter));

                regexFilter = (Boolean) parser.getOptionValue(regexDirMergeFilter, defaultRegExFilter);
                fileCopierProperties.setProperty("RegExDirMergeFilter", Boolean.toString(regexFilter));

                exclusiveFilter = (Boolean) parser.getOptionValue(exclusiveDirMergeFilter, defaultExclusiveFilter);
                fileCopierProperties.setProperty("ExclusiveDirMergeFilter", Boolean.toString(exclusiveFilter));

                boolean promptForRestore = (Boolean) parser.getOptionValue(promptRestore, defaultPromptForRestore);
                fileCopierProperties.setProperty("PromptForRestore", Boolean.toString(promptForRestore));

                boolean promptForOverwrite = (Boolean) parser.getOptionValue(promptOverwrite, defaultPromptForOverwrite);
                fileCopierProperties.setProperty("PromptForOverwrite", Boolean.toString(promptForOverwrite));

                String regexString = (String) parser.getOptionValue(fileNameCopyFilter, defaultRegExFilenameFilterStr);
                fileCopierProperties.setProperty("FileNameCopyFilter", regexString);

                regexString = (String) parser.getOptionValue(dirNameCopyFilter, defaultRegExFilenameFilterStr);
                fileCopierProperties.setProperty("DirNameCopyFilter", regexString);

                regexString = (String) parser.getOptionValue(dirNameFlattenFilter, defaultRegExFilenameFilterStr);
                fileCopierProperties.setProperty("DirNameFlattenFilter", regexString);

                regexString = (String) parser.getOptionValue(dirNameMergeFilter, defaultRegExFilenameFilterStr);
                fileCopierProperties.setProperty("DirNameMergeFilter", regexString);

                String logLevelStr = (String) parser.getOptionValue(logLevel, defaultLogLevel.toString());
                // See if the level we got from the command line is different than what is in the properties file. If it is
                // different, we need to update the log4j.xml file, so set logLevelChanged to true.
                String prevLogLevelStr = fileCopierProperties.getProperty("LogLevel");
                if (prevLogLevelStr == null || !prevLogLevelStr.equalsIgnoreCase(logLevelStr)) {
                    fileCopierProperties.setProperty("LogLevel", logLevelStr);
                    logLevelChanged = true;
                }
                // Set the log logLevel in the log4j.xml file that is in the filecopier.jar file.

                int maxMergeLevel = (Integer) parser.getOptionValue(maxMerge, defaultMaxDirMergeLevel);
                fileCopierProperties.setProperty("MaxDirMergeLevel", Integer.toString(maxMergeLevel));

                int maxFlattenLevel = (Integer) parser.getOptionValue(maxFlatten, defaultMaxDirFlattenLevel);
                fileCopierProperties.setProperty("MaxDirFlattenLevel", Integer.toString(maxFlattenLevel));

                int maxCopyLevel = (Integer) parser.getOptionValue(maxCopy, defaultMaxDirCopyLevel);
                fileCopierProperties.setProperty("MaxDirCopyLevel", Integer.toString(maxCopyLevel));

                boolean noBuildRestoreSct = (Boolean) parser.getOptionValue(noBuildRestoreScript, false);
                fileCopierProperties.setProperty("BuildRestoreScript", Boolean.toString(!noBuildRestoreSct));

                boolean updateOnly = (Boolean) parser.getOptionValue(update, defaultUpdateOnly);
                fileCopierProperties.setProperty("UpdateOnly", Boolean.toString(updateOnly));

                String userCopyScriptPath = (String) parser.getOptionValue(setCopyScriptPathOption);
                if (userCopyScriptPath != null) {
                    setCopyScriptPath = true;
                    userCopyScriptPath = setPath(userCopyScriptPath, fileCopierProperties);
                    if (userCopyScriptPath != null && userCopyScriptPath.length() > 0) {
                        fileCopierProperties.setProperty("CopyPath", userCopyScriptPath);
                    }
                }

                String userRestoreScriptPath = (String) parser.getOptionValue(setRestoreScriptPathOption);
                if (userRestoreScriptPath != null) {
                    setRestoreScriptPath = true;
                    userRestoreScriptPath = setPath(userRestoreScriptPath, fileCopierProperties);
                    if (userRestoreScriptPath != null && userRestoreScriptPath.length() > 0) {
                        fileCopierProperties.setProperty("RestorePath", userRestoreScriptPath);
                    }
                }

                getCopyScriptPath = (Boolean) parser.getOptionValue(getCopyScriptPathOption, false);
                getRestoreScriptPath = (Boolean) parser.getOptionValue(getRestoreScriptPathOption, false);
                getVersion = (Boolean) parser.getOptionValue(getVersionOption, false);
            } catch (IOException e) {
                throw new FileCopierException(e, e);
            } finally {
                try {
                    fileCopierProperties.store(fos, null);
                    fos.close();
                } catch (IOException e) {
                    throw new FileCopierException(e, e);
                }
            }
        }

    }

    public boolean printCopyScriptPath() {
        return getCopyScriptPath;
    }

    public boolean printRestoreScriptPath() {
        return getRestoreScriptPath;
    }
    
    public boolean printVersion() {
        return getVersion;
    }

    private String setPath(String scriptPath, final Properties properties) {
        // Look for property values.
        Pattern p = Pattern.compile("\\$\\{?[\\w\\.?]+\\}?");
        Matcher m = p.matcher(scriptPath);
        StringBuilder dest = new StringBuilder();
        boolean appendToDest = false;
        int start = 0;
        int end = 0;
        int startIdx = 0;

        while (m.find()) {
            start = m.start();
            end = m.end();
            String value = scriptPath.substring(start, end);

            // Get the name of the property value.
            Pattern p2 = Pattern.compile("[\\w\\.?]+");
            Matcher m2 = p2.matcher(value);
            if (m2.find()) {
                int startVal = m2.start();
                int endVal = m2.end();
                value = value.substring(startVal, endVal);

                // See if the property value is "filecopier.scripts.copy"
                if (value.equals("filecopier.scripts.copy")) {
                    // Get the copy path from the properties file.
                    String copyPathStr = properties.getProperty("CopyPath", System.getProperty("filecopier.scripts.copy"));

                    dest.append(scriptPath.substring(startIdx, start));
                    dest.append(copyPathStr);
                    startIdx = end;
                    appendToDest = true;
                } else if (value.equals("filecopier.scripts.restore")) {
                    // Get the restore path from the properties file.
                    String restorePathStr = properties.getProperty("RestorePath", System.getProperty("filecopier.scripts.restore"));

                    dest.append(scriptPath.substring(startIdx, start));
                    dest.append(restorePathStr);
                    startIdx = end;
                    appendToDest = true;
                } else {
                    String propVal = System.getProperty(value);
                    if (propVal != null) {
                        dest.append(scriptPath.substring(startIdx, start));
                        dest.append(propVal);
                        startIdx = end;
                        appendToDest = true;
                    }
                }
            }
        }

        if (appendToDest) {
            dest.append(scriptPath.substring(end));
            scriptPath = dest.toString();
        }
        // Make setPath true. This will tell us that the user set the path on the command line. If that's all the
        // user put on the command line and he ran it in console mode (-c), this is a legitimate option and we
        // will not print out a usage() message.
        //setPath = true;
        return scriptPath;
    }

    public boolean wasCopyScriptPathPathSet() {
        return setCopyScriptPath;
    }

    public boolean wasRestoreScriptPathSet() {
        return setRestoreScriptPath;
    }

    private void removeDeletedScripts(int idx) {
        String[] items = mru[idx].getItems();
        boolean[] validIdxs = new boolean[items.length];
        int validCt = 0;
        boolean foundDeleted = false;

        for (int i = 0; i < items.length; ++i) {
            try {
                filecopier.script.CopyScript script = new filecopier.script.CopyScript(new File(items[i]));
                validIdxs[i] = true;
                ++validCt;
            } catch (IOException e) {
                validIdxs[i] = false;
                foundDeleted = true;
            } catch (custom.xml.DOMHelperException e) {
                validIdxs[i] = false;
                foundDeleted = true;
            }
        }

        if (foundDeleted) {
            String[] editedItems = new String[validCt];
            int j, k;
            for (j = k = 0; j < validIdxs.length; ++j) {
                if (validIdxs[j] == true) {
                    editedItems[k++] = items[j];
                }
            }
            mru[idx].removeAll();
            mru[idx].addAll(editedItems);
        }
    }

    public void setBuildRestoreScript(final boolean buildRestoreScript) {
        synchronized (this.buildRestoreScript) {
            this.buildRestoreScript = buildRestoreScript;
        }
    }

    public void setCaseSensitiveFilter(final SrchFilter sf, final boolean caseSensitiveFileFilter) {
        int idx = sf.ordinal();
        synchronized (this.caseSensitiveFilter[idx]) {
            this.caseSensitiveFilter[idx] = caseSensitiveFileFilter;
        }
    }

    public void setMaxDirFlattenLevel(int maxFlattenLevel) {
        synchronized (this.maxDirLevels[MAX_DIR_FLATTEN_IDX]) {
            this.maxDirLevels[MAX_DIR_FLATTEN_IDX] = maxFlattenLevel;
        }
    }

    public void setExclusiveFilter(final SrchFilter sf, final boolean exclusiveFilter) {
        int idx = sf.ordinal();
        synchronized (this.exclusiveFilter[idx]) {
            this.exclusiveFilter[idx] = exclusiveFilter;
        }
    }

    public void setLogLevel(final Level logLevel) {
        synchronized (this.logLevel) {
            if (!this.logLevel.equals(logLevel)) {
                logLevelChanged = true;
            }
            this.logLevel = logLevel;
        }
    }

    public void setLookAndFeel(final UIManager.LookAndFeelInfo lfi) {
        synchronized (this.activeLookAndFeel) {
            this.activeLookAndFeel = lfi;
        }
    }

    private Level setLogLevel(final CmdLineParser.Option ll, final String logLevelStr) throws CmdLineParser.IllegalOptionValueException {
        Level logLevel = null;

        if (logLevelStr != null) {
            if (logLevelStr.equalsIgnoreCase("debug")) {
                logLevel = Level.DEBUG;
            } else if (logLevelStr.equalsIgnoreCase("info")) {
                logLevel = Level.INFO;
            } else if (logLevelStr.equalsIgnoreCase("warn")) {
                logLevel = Level.WARN;
            } else if (logLevelStr.equalsIgnoreCase("error")) {
                logLevel = Level.ERROR;
            } else {
                throw new CmdLineParser.IllegalOptionValueException(ll, logLevelStr);
            }
        }
        return logLevel;
    }

    public void setMaxDirLevel(int idx, int maxDirLevel) {
        if (idx < MAX_DIR_COPY_IDX || idx > MAX_DIR_MERGE_IDX) {
            throw new IllegalArgumentException("setMaxDirLevel() was passed an index that is out of range.");
        }
        synchronized (maxDirLevels[idx]) {
            maxDirLevels[idx] = maxDirLevel;
        }
    }

    public void setMaxDirCopyLevel(final int maxDirLevel) {
        synchronized (this.maxDirLevels[MAX_DIR_COPY_IDX]) {
            this.maxDirLevels[MAX_DIR_COPY_IDX] = maxDirLevel;
        }
    }

    public void setMaxDirMergeLevel(int maxMergeLevel) {
        synchronized (this.maxDirLevels[MAX_DIR_MERGE_IDX]) {
            this.maxDirLevels[MAX_DIR_MERGE_IDX] = maxMergeLevel;
        }
    }

    // This method sets all options EXCEPT logLevel, copyPath, and restorePathWrapper to their default values. It does not set
    // copyPath or restorePathWrapper to their defaults because the user may be requesting a print out of this value, or they
    // may be appending more paths to its value. logLevel is not set to its default value because I need to check the 
    // value I get from the user to see if it is the same as when FileCopier was previously run. If they are not the
    // same, I need to change the values in the log4j.xml file. If they are the same, I leave the log4j.xml file alone.
    private void setOptionsToDefaults() {
        fileCopierProperties.setProperty("BuildRestoreScript", defaultBuildRestoreScript.toString());
        fileCopierProperties.setProperty("MaxDirFlattenLevel", defaultMaxDirFlattenLevel.toString());
        fileCopierProperties.setProperty("MaxDirMergeLevel", defaultMaxDirMergeLevel.toString());
        fileCopierProperties.setProperty("PromptForOverwrite", defaultPromptForOverwrite.toString());
        fileCopierProperties.setProperty("PromptForRestore", defaultPromptForRestore.toString());
        fileCopierProperties.setProperty("UpdateOnly", defaultUpdateOnly.toString());
        fileCopierProperties.setProperty("GMT", defaultUseGMT.toString());
        fileCopierProperties.setProperty("MaxDirCopyLevel", defaultMaxDirCopyLevel.toString());
        fileCopierProperties.setProperty("RenameFmtString", defaultRenameFmtStr);
        fileCopierProperties.setProperty("Tab", defaultTabIndex.toString());
        fileCopierProperties.setProperty("IfFileExists", defaultRenameType.toString());

        for (int i = FILE_COPY_FILTER; i <= DIR_MERGE_FILTER; i++) {
            fileCopierProperties.setProperty(regExFilenameFilterKeys[i], defaultRegExFilenameFilterStr);
            fileCopierProperties.setProperty(caseSensitiveFilterKeys[i], defaultCaseSensitiveFilter.toString());
            fileCopierProperties.setProperty(regExFilterKeys[i], defaultRegExFilter.toString());
            fileCopierProperties.setProperty(exclusiveFilterKeys[i], defaultExclusiveFilter.toString());
        }
    }

    public void setPromptForOverwrite(final boolean promptForOverwrite) {
        synchronized (this.promptForOverwrite) {
            this.promptForOverwrite = promptForOverwrite;
        }
    }

    public void setPromptForRestore(final boolean promptForRestore) {
        synchronized (this.promptForRestore) {
            this.promptForRestore = promptForRestore;
        }
    }

    public void setRegExFileFilter(final SrchFilter sf, final boolean regExFilter) {
        int idx = sf.ordinal();
        synchronized (this.regExFilter[idx]) {
            this.regExFilter[idx] = regExFilter;
        }
    }

    public void setRegExFilenameFilter(SrchFilter sf, final FilenameFilter filenameFilter) {
        int idx = sf.ordinal();
        synchronized (this.regExFilenameFilter[idx]) {
            this.regExFilenameFilter[idx] = filenameFilter;
        }
    }

    public FilenameFilter setRegExFilenameFilter(SrchFilter sf, String nameFilter, boolean filterCaseSensitive, boolean filterExclusive,
            boolean filterRegex, boolean filterSubname, boolean filterFilename, boolean toAccept) {
        FilenameFilter filenameFilter = null;

        if (nameFilter != null && nameFilter.length() > 0) {
            // Calculate the getRegexFlags
            int regexFlags = 0;
            if (!filterCaseSensitive) {
                regexFlags += Pattern.CASE_INSENSITIVE;
            }
            if (!filterRegex) {
                regexFlags += Pattern.LITERAL;
            }

            // Calculate the filenameFlags
            int filenameFlags = 0;
            if (filterExclusive) {
                filenameFlags += RegExFilenameFilter.EXCLUSIVE;
            }
            if (filterFilename) {
                filenameFlags += RegExFilenameFilter.FILTERFILENAMES;
            }
            if (filterSubname) {
                filenameFlags += RegExFilenameFilter.FILTERDIRNAMES;
            }

            filenameFilter = new RegExFilenameFilter(nameFilter, regexFlags, filenameFlags);
        } else {
            filenameFilter = new NullFilenameFilter(toAccept);
        }

        return filenameFilter;
    }

    public void setRenameType(final RenameType renameType) {
        synchronized (this.renameType) {
            this.renameType = renameType;
        }
    }

    private Options.RenameType setRenameType(CmdLineParser.Option fileExistAction, String renameTypeStr) throws CmdLineParser.OptionException {
        Options.RenameType renameType = null;

        //Overwrite, Skip, RenameAuto, RenameManual

        if (renameTypeStr.equals("RenameAuto")) {
            renameType = Options.RenameType.RenameAuto;
        } else if (renameTypeStr.equals("RenameManual")) {
            renameType = Options.RenameType.RenameManual;
        } else if (renameTypeStr.equals("Skip")) {
            renameType = Options.RenameType.Skip;
        } else if (renameTypeStr.equals("Overwrite")) {
            renameType = Options.RenameType.Overwrite;
        } else {
            throw new CmdLineParser.IllegalOptionValueException(fileExistAction, renameTypeStr);
        }
        return renameType;
    }

    public void setTabIndex(final TabIndex tabIndex) {
        this.tabIndex = tabIndex;
    }

    public void setUpdateOnly(final boolean updateOnly) {
        synchronized (this.updateOnly) {
            this.updateOnly = updateOnly;
        }
    }

    public void setUseGMT(final boolean useGMT) {
        synchronized (this.useGMT) {
            this.useGMT = useGMT;
        }
    }

    private void storeBuildRestoreScript() {
        synchronized (buildRestoreScript) {
            fileCopierProperties.setProperty("BuildRestoreScript", buildRestoreScript.toString());
        }
    }
    
    private void storeUseWindowDecorations() {
        fileCopierProperties.setProperty("WindowDecorations", useWindowDecorations.toString());
    }

    private void storeCaseSensitiveFilter(SrchFilter sf) {
        int idx = sf.ordinal();
        synchronized (caseSensitiveFilter[idx]) {
            fileCopierProperties.setProperty(caseSensitiveFilterKeys[idx], caseSensitiveFilter[idx].toString());
        }
    }

    private void storeCopyPath() {
        synchronized (copyPath) {
            fileCopierProperties.setProperty("CopyPath", copyPath.getPath());
        }
    }

    private void storeMaxDirFlattenLevel() {
        synchronized (maxDirLevels[MAX_DIR_FLATTEN_IDX]) {
            fileCopierProperties.setProperty("MaxDirFlattenLevel", maxDirLevels[MAX_DIR_FLATTEN_IDX].toString());
        }
    }

    private void storeExclusiveFilter(SrchFilter sf) {
        int idx = sf.ordinal();
        synchronized (exclusiveFilter[idx]) {
            fileCopierProperties.setProperty(exclusiveFilterKeys[idx], exclusiveFilter[idx].toString());
        }
    }

    /**
     * This method sets the properties in fileCopierProperties that pertain to the window size/location.
     *
     * @return void
     */
    private void storeFrameProperties(final Dimension size, final Point location, final int extendedState) {
        synchronized (fileCopierProperties) {
            fileCopierProperties.setProperty("Window.state", Integer.toString(extendedState));

            if (extendedState != Frame.MAXIMIZED_BOTH) {
                // Get the window size and location.
                fileCopierProperties.setProperty("Window.height", Integer.toString(size.height));
                fileCopierProperties.setProperty("Window.width", Integer.toString(size.width));
                fileCopierProperties.setProperty("Window.x", Integer.toString(location.x));
                fileCopierProperties.setProperty("Window.y", Integer.toString(location.y));
            }
        }
    }

    private void storeLookAndFeel() {
        synchronized (activeLookAndFeel) {
            fileCopierProperties.setProperty("LookAndFeel", activeLookAndFeel.getName());
        }
    }

    private void storeLogLevel() {
        synchronized (logLevel) {
            fileCopierProperties.setProperty("LogLevel", logLevel.toString());
        }
    }

    private void storeMaxDirCopyLevel() {
        synchronized (maxDirLevels[MAX_DIR_COPY_IDX]) {
            fileCopierProperties.setProperty("MaxDirCopyLevel", maxDirLevels[MAX_DIR_COPY_IDX].toString());
        }
    }

    private void storeMaxDirMergeLevel() {
        synchronized (maxDirLevels[MAX_DIR_MERGE_IDX]) {
            fileCopierProperties.setProperty("MaxDirMergeLevel", maxDirLevels[MAX_DIR_MERGE_IDX].toString());
        }
    }

    private void storePromptForOverwrite() {
        synchronized (promptForOverwrite) {
            fileCopierProperties.setProperty("PromptForOverwrite", promptForOverwrite.toString());
        }
    }

    private void storePromptForRestore() {
        synchronized (promptForRestore) {
            fileCopierProperties.setProperty("PromptForRestore", promptForRestore.toString());
        }
    }

    /**
     * This method sets and stores all the properties in fileCopierProperties to the filecopier.properties file.
     *
     * 
     */
    public void storeProperties(final Dimension size, final Point location, final int extendedState) {
        FileOutputStream fos = null;
        try {
            synchronized (fileCopierIni) {
                fos = new FileOutputStream(fileCopierIni);
            }

            storeFrameProperties(size, location, extendedState);
            storeLookAndFeel();
            storeTabIndex();
            storeBuildRestoreScript();
            storePromptForOverwrite();
            storePromptForRestore();
            storeMaxDirFlattenLevel();
            storeMaxDirMergeLevel();
            storeRenameType();
            storeLogLevel();
            storeScripts(FROM_IDX);
            storeScripts(TO_IDX);
            storeUpdateOnly();
            storeRenameFmtString();
            storeUseGMT();
            storeMaxDirCopyLevel();
            storeCopyPath();
            storeRestorePath();
            storeUseWindowDecorations();

            for (SrchFilter sf : srchFilterValues) {
                storeCaseSensitiveFilter(sf);
                storeRegExFilter(sf);
                storeExclusiveFilter(sf);
                storeRegExFilenameFilter(sf);
            }
            fileCopierProperties.store(fos, null);

        } catch (IOException e) {
            Singleton.getInstance().getFileLogger().error("IOException", e);
        } catch (MRUException e) {
            Singleton.getInstance().getFileLogger().error("MRUException", e);
        } finally {
            try {
                fos.close();
            } catch (IOException e) {
                Singleton.getInstance().getFileLogger().error("IOException", e);
            }
        }
    }

    public void storeProperties() {
        FileOutputStream fos = null;
        try {
            synchronized (fileCopierIni) {
                fos = new FileOutputStream(fileCopierIni);
            }
            storeBuildRestoreScript();
            storePromptForOverwrite();
            storePromptForRestore();
            storeMaxDirFlattenLevel();
            storeMaxDirMergeLevel();
            storeRenameType();
            storeLogLevel();
            storeScripts(FROM_IDX);
            storeScripts(TO_IDX);
            storeUpdateOnly();
            storeRenameFmtString();
            storeUseGMT();
            storeMaxDirCopyLevel();
            storeCopyPath();
            storeRestorePath();

            for (SrchFilter sf : srchFilterValues) {
                storeCaseSensitiveFilter(sf);
                storeRegExFilter(sf);
                storeExclusiveFilter(sf);
                storeRegExFilenameFilter(sf);
            }
            fileCopierProperties.store(fos, null);
            fos.close();
        } catch (IOException e) {
            Singleton.getInstance().getFileLogger().error("IOException", e);
            Singleton.getInstance().getConsoleLogger().error("IOException: " + e.getMessage());
        } catch (MRUException e) {
            Singleton.getInstance().getFileLogger().error("MRUException", e);
            Singleton.getInstance().getConsoleLogger().error("MRUException: " + e.getMessage());
        } finally {
            try {
                fos.close();
            } catch (IOException e) {
                Singleton.getInstance().getFileLogger().error("IOException", e);
                Singleton.getInstance().getConsoleLogger().error("IOException: " + e.getMessage());
            }
        }
    }

    private void storeRegExFilenameFilter(SrchFilter sf) {
        int idx = sf.ordinal();
        synchronized (regExFilenameFilter[idx]) {
            fileCopierProperties.setProperty(regExFilenameFilterKeys[idx],
                    regExFilenameFilter[idx] instanceof NullFilenameFilter ? "" : ((RegExFilenameFilter) regExFilenameFilter[idx]).pattern());
        }
    }

    private void storeRegExFilter(SrchFilter sf) {
        int idx = sf.ordinal();
        synchronized (regExFilter[idx]) {
            fileCopierProperties.setProperty(regExFilterKeys[idx], regExFilter[idx].toString());
        }
    }

    private void storeRenameFmtString() {
        synchronized (renameFmtString) {
            fileCopierProperties.setProperty("RenameFmtString", renameFmtString.getRenameFmtString());
        }
    }

    private void storeRenameType() {
        synchronized (renameType) {
            String renameText;
            if (renameType == RenameType.RenameAuto) {
                renameText = new String("RenameAuto");
            } else if (renameType == RenameType.RenameManual) {
                renameText = new String("RenameManual");
            } else if (renameType == RenameType.Overwrite) {
                renameText = new String("Overwrite");
            } else {
                renameText = new String("Skip");
            }
            fileCopierProperties.setProperty("IfFileExists", renameText);
        }
    }

    private void storeRestorePath() {
        synchronized (restorePathWrapper) {
            fileCopierProperties.setProperty("RestorePath", restorePathWrapper.getRestorePath().getPath());
        }
    }

    private void storeScripts(final int idx) throws MRUException {
        synchronized (mru) {
            removeDeletedScripts(idx);
            mru[idx].saveInProperties(fileCopierProperties);
        }
    }

    private void storeTabIndex() {
        synchronized (tabIndex) {
            fileCopierProperties.setProperty("Tab", tabIndex.toString());
        }
    }

    private void storeUpdateOnly() {
        synchronized (updateOnly) {
            fileCopierProperties.setProperty("UpdateOnly", updateOnly.toString());
        }
    }

    private void storeUseGMT() {
        synchronized (useGMT) {
            fileCopierProperties.setProperty("GMT", useGMT.toString());
        }
    }

    public static void updateLookAndFeel(final String lookAndFeelClassName, final java.awt.Window w) {
        try {
            UIManager.setLookAndFeel(lookAndFeelClassName);
            javax.swing.SwingUtilities.updateComponentTreeUI(w);
        } catch (Exception e) {
        // It should set to the default look and feel.
        }
    }

    public static String validateRenameFmtString(final String internalFmtString, Calendar cal) {
        boolean validFmt = false;
        Formatter fmt = new Formatter();

        if (cal != null) {
            try {
                fmt.format(internalFmtString, cal);
                validFmt = true;
            } catch (java.util.IllegalFormatException efe) {
                validFmt = false;
            }
        }

        return (validFmt ? fmt.toString() : null);
    }

    public static class NullFilenameFilter implements FilenameFilter {

        private boolean toAccept = true;

        public NullFilenameFilter() {
        }

        public NullFilenameFilter(boolean toAccept) {
            this.toAccept = toAccept;
        }

        public boolean accept(File dir, String name) {
            return toAccept;
        }
    }
}
